<!DOCTYPE html>
<html>
<head>
    <title>opentype.js – JavaScript parser/writer for OpenType and TrueType fonts.</title>
    <meta name="description" content="A JavaScript library to manipulate the letterforms of text from the browser or node.js.">
    <meta charset="utf-8">
    <!-- Note that the internal development server rewrites the JavaScript URL below to build/opentype.js -->
    <script type="text/javascript" src="dist/opentype.js"></script>
    <link rel="stylesheet" href="site.css">
</head>
<body>
<div class="header">
    <div class="container">
        <h1>opentype.js</h1>
        <nav>
            <a href="index.html">Home</a>
            <a href="font-inspector.html">Font Inspector</a>
            <a href="glyph-inspector.html">Glyph Inspector</a>
        </nav>
        <nav class="right">
            <a class="github" href="https://github.com/opentypejs/opentype.js">Fork me on GitHub</a>
            <a class="gitter" href="https://gitter.im/opentypejs/opentype.js">Chat on Gitter</a>
        </nav>
    </div>
</div>

<div class="container">

    <div class="explain">
        opentype.js is an OpenType and TrueType font parser and writer.
        It allows you to access the <strong>letterforms</strong> of text from the browser or node.js.
    </div>

    <input id="file" type="file">
    <span class="info" id="font-name">Fira Sans</span>
    <canvas id="preview" width="940" height="300" class="text"></canvas>
    <div id="message"></div>
    <input type="text" class="text-input" onkeyup="renderText()" value="Hello, World!"
           autofocus id="textField">
    <label>Font Size<input type="range" min="6" max="500" step="2" value="150" id="font-size-range" autocomplete="off"><span id="fontSize">150</span></label>
    <label><input type="checkbox" onchange="drawPointsChanged(this)" checked="checked">Draw Points</label>
    <label><input type="checkbox" onchange="drawMetricsChanged(this)">Draw Metrics</label>
    <label><input type="checkbox" onchange="kerningChanged(this)" checked="checked">Kerning</label>
    <label><input type="checkbox" onchange="ligaturesChanged(this)" checked="checked">Ligatures</label>
    <label id="hinting-label" class="disabled"><input id="hinting-checkbox" type="checkbox" onchange="hintingChanged(this)" disabled="true">Hinting</label>

    <hr>

    <div class="explain">
        Once you have the shapes, you can modify them, for example by <strong>snapping</strong> them to a virtual grid:
    </div>

    <canvas id="snap" width="940" height="300" class="text"></canvas>
    <label>Strength <input type="range" min="0" max="100" value="80" oninput="snapStrengthChanged(this)"/><span id="snapStrength">80</span></label>
    <label>Distance<input type="range" min="1" max="100" value="53" oninput="snapDistanceChanged(this)"/><span id="snapDistance">53</span></label>
    <label>X<input type="range" min="-100" max="100" value="0" oninput="snapXChanged(this)"/><span id="snapX">0</span></label>
    <label>Y<input type="range" min="-100" max="100" value="0" oninput="snapYChanged(this)"/><span id="snapY">0</span></label>

    <hr>

    <div class="explain">
        <h1>Glyphs</h1>
        opentype.js provides you with <strong>raw access</strong> to the glyphs so you can modify them as you please.
    </div>

    <div id="glyphs">
    </div>
    <div class="message">Only the first 100 glyphs are shown.</div>

    <hr>

    <div class="explain">
        <h1>Free Software</h1>
        <p>opentype.js is available on <a href="https://github.com/opentypejs/opentype.js">GitHub</a> under the <a href="https://raw.github.com/opentypejs/opentype.js/master/LICENSE">MIT License</a>.</p>
        <p>Copyright &copy; 2017 Frederik De Bleser.</p>
    </div>

    <hr>
</div>


<script type="text/javascript">
var font = null;
var fontSize = 150;
var textToRender = "Hello, World!";
var drawPoints = true;
var drawMetrics = false;
var kerning = true;
var ligatures = true;
var hinting = false;
var previewPath = null;
var snapPath = null;
var snapStrength = 80;
var snapDistance = 53;
var snapX = 0;
var snapY = 0;
var fontSizeSlider = document.getElementById("font-size-range");
var hintingCheckbox = document.getElementById("hinting-checkbox");
var hintingLabel = document.getElementById("hinting-label");

function drawPointsChanged(e) {
    drawPoints = e.checked;
    renderText();
}

function drawMetricsChanged(e) {
    drawMetrics = e.checked;
    renderText();
}

function kerningChanged(e) {
    kerning = e.checked;
    renderText();
}

function ligaturesChanged(e) {
    ligatures = e.checked;
    renderText();
}

function hintingChanged(e) {
    hinting = e.checked;
    renderText();
}

function fontSizeChanged() {
    fontSize = parseInt(fontSizeSlider.value, 10);
    document.getElementById('fontSize').innerHTML = '' + fontSize;
    renderText();
}

function snapStrengthChanged(e) {
    snapStrength = e.value;
    document.getElementById('snapStrength').innerHTML = '' + snapStrength;
    renderText();
}

function snapDistanceChanged(e) {
    snapDistance = e.value;
    document.getElementById('snapDistance').innerHTML = '' + snapDistance;
    renderText();
}

function snapXChanged(e) {
    snapX = e.value * 1.0;
    document.getElementById('snapX').innerHTML = '' + snapX;
    renderText();
}

function snapYChanged(e) {
    snapY = e.value * 1.0;
    document.getElementById('snapY').innerHTML = '' + snapY;
    renderText();
}

// Round a value to the nearest "step".
function snap(v, distance, strength) {
    return (v * (1.0 - strength)) + (strength * Math.round(v / distance) * distance);
}

function doSnap(path) {
    var i;
    var strength = snapStrength / 100.0;
    for (i = 0; i < path.commands.length; i++) {
        var cmd = path.commands[i];
        if (cmd.type !== 'Z') {
            cmd.x = snap(cmd.x + snapX, snapDistance, strength) - snapX;
            cmd.y = snap(cmd.y + snapY, snapDistance, strength) - snapY;
        }
        if (cmd.type === 'Q' || cmd.type === 'C') {
            cmd.x1 = snap(cmd.x1 + snapX, snapDistance, strength) - snapX;
            cmd.y1 = snap(cmd.y1 + snapY, snapDistance, strength) - snapY;
        }
        if (cmd.type === 'C') {
            cmd.x2 = snap(cmd.x2 + snapX, snapDistance, strength) - snapX;
            cmd.y2 = snap(cmd.y2 + snapY, snapDistance, strength) - snapY;
        }
    }
}

function renderText() {
    if (!font) return;
    textToRender = document.getElementById('textField').value;
    var previewCtx = document.getElementById('preview').getContext('2d');
    var options = {
        kerning: kerning,
        hinting: hinting,
        features: {
            liga: ligatures,
            rlig: ligatures
        }
    };
    previewCtx.clearRect(0, 0, 940, 300);
    font.draw(previewCtx, textToRender, 0, 200, fontSize, options);
    if (drawPoints) {
        font.drawPoints(previewCtx, textToRender, 0, 200, fontSize, options);
    }
    if (drawMetrics) {
        font.drawMetrics(previewCtx, textToRender, 0, 200, fontSize, options);
    }

    snapPath = font.getPath(textToRender, 0, 200, fontSize, options);
    doSnap(snapPath);
    var snapCtx = document.getElementById('snap').getContext('2d');
    snapCtx.clearRect(0, 0, 940, 300);
    snapPath.draw(snapCtx);
}

function enableHighDPICanvas(canvas) {
    if (typeof canvas === 'string') {
        canvas = document.getElementById(canvas);
    }
    var pixelRatio = window.devicePixelRatio || 1;
    if (pixelRatio === 1) return;
    var oldWidth = canvas.width;
    var oldHeight = canvas.height;
    canvas.width = oldWidth * pixelRatio;
    canvas.height = oldHeight * pixelRatio;
    canvas.style.width = oldWidth + 'px';
    canvas.style.height = oldHeight + 'px';
    canvas.getContext('2d').scale(pixelRatio, pixelRatio);
}

// Create a canvas and adds it to the document.
// Returns the 2d drawing context.
function createGlyphCanvas(glyph, size) {
    var canvasId, html, glyphsDiv, wrap, canvas, ctx, pixelRatio;
    canvasId = 'c' + glyph.index;
    html = '<div class="wrapper" style="width:' + size + 'px"><canvas id="' + canvasId + '" width="' + size + '" height="' + size + '"></canvas><span>' + glyph.index + '</span></div>';
    glyphsDiv = document.getElementById('glyphs');
    wrap = document.createElement('div');
    wrap.innerHTML = html;
    glyphsDiv.appendChild(wrap);
    canvas = document.getElementById(canvasId);
    ctx = canvas.getContext('2d');
    enableHighDPICanvas(canvas, ctx);
    return ctx;
}

function showErrorMessage(message) {
    var el = document.getElementById('message');
    if (!message || message.trim().length === 0) {
        el.style.display = 'none';
    } else {
        el.style.display = 'block';
    }
    el.innerHTML = message;
}

function onFontLoaded(font) {
    var glyphsDiv, i, x, y, fontSize;
    window.font = font;

    // Show the first 100 glyphs.
    glyphsDiv = document.getElementById('glyphs');
    glyphsDiv.innerHTML = '';

    amount = Math.min(100, font.glyphs.length);
    x = 50;
    y = 120;
    fontSize = 72;
    for (i = 0; i < amount; i++) {
        glyph = font.glyphs.get(i);
        ctx = createGlyphCanvas(glyph, 150);
        glyph.draw(ctx, x, y, fontSize);
        glyph.drawPoints(ctx, x, y, fontSize);
        glyph.drawMetrics(ctx, x, y, fontSize);
    }

    if (font.hinting) {
        hintingCheckbox.disabled = false;
        hintingLabel.className = '';
    } else {
        hintingCheckbox.disabled = true;
        hintingCheckbox.checked = false;
        hintingLabel.className = 'disabled';
    }

    renderText();
}

function onReadFile(e) {
    document.getElementById('font-name').innerHTML = '';
    var file = e.target.files[0];
    var reader = new FileReader();
    reader.onload = function(e) {
        try {
            font = opentype.parse(e.target.result);
            onFontLoaded(font);
            showErrorMessage('');
        } catch (err) {
            showErrorMessage(err.toString());
        }
    };
    reader.onerror = function(err) {
        showErrorMessage(err.toString());
    };

    reader.readAsArrayBuffer(file);
}

var fontFileName = 'fonts/FiraSansMedium.woff';

document.getElementById('font-name').innerHTML = fontFileName.split('/')[1];

var fileButton = document.getElementById('file');
fileButton.addEventListener('change', onReadFile, false);

// FireFox & Chrome fire the 'input' event continuously, then the 'change' event on mouse up.
// IE 11 doesn't fire the 'input' event at all, but che 'change' event continuously.
fontSizeSlider.addEventListener('input', fontSizeChanged, false);
fontSizeSlider.addEventListener('change', fontSizeChanged, false);

enableHighDPICanvas('preview');
enableHighDPICanvas('snap');

opentype.load(fontFileName, function(err, font) {
    var amount, glyph, ctx, x, y, fontSize;
    if (err) {
        showErrorMessage(err.toString());
        return;
    }
    onFontLoaded(font);
});
</script>
</body>
</html>
